﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Images;
using System.Drawing;
using Silvermoon.Core;
using Silvermoon.Interfaces;
using Silvermoon.Utils;
using Silvermoon.Controls;
using Silvermoon.OpenGL;

namespace Silvermoon.Shapes
{

    #region enums

    public interface IBubble
    {
        Placement ArrowPlacement { get; set; }
        int ArrowOffset { get; set; }
        Rectangle GetTilesRect();


        /// <summary>
        /// Gets the space which is required vertically to display the bubble hull.
        /// </summary>
        /// <returns></returns>
        int GetVerticalSpace();
    }

    public enum Placement
    {
        Bottom,
        Top,
        Left,
        Right,
        Hide
    }

    #endregion

    /// <summary>
    /// Shape of a bubble with an arrow at either on the bottom, top, left or right and customizable location of the arrow and multiple tiles, where one tile can be selected.
    /// </summary>
    public class BubbleShape : Shape, IColor, IBubble
    {
        #region fields

        const int arrowOverlapAtTop = 3;
        private RenderStyle renderStyle = RenderStyle.Plastic;
        private Color color;
        private Color selectedColor;
        private Placement arrowPlacement;
        private int arrowOffset;
        private BackgroundShape[] tileShapes;
        private BackgroundShape arrowShape;
        private int numTiles = 1;
        private int selectedTile = -1;

        private static Transformation[] arrowTopTransformation = new Transformation[]
        {
            new RotateTransform{ Axis= RotateAxis.ZAxis, Angle=180}
        };

        #endregion
        #region ctors/dtors

        public BubbleShape()
            : base()
        {
            arrowShape = new BackgroundShape
            {
                Texture = SysImages.BubbleArrow,
                Style = RenderStyle.Simple,
                Size = SysImages.BubbleArrow.Size
            };
        }

        public override void Dispose()
        {
            DisposeTiles();
            arrowShape.Dispose();
            base.Dispose();
        }

        #endregion
        #region props

        public RenderStyle RenderStyle
        {
            get { return renderStyle; }
            set
            {
                if (renderStyle != value)
                {
                    renderStyle = value;
                    DisposeTiles();
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or sets the number of tiles which must be within 1 and 10.
        /// </summary>
        public int TileCount
        {
            get { return numTiles; }
            set
            {
                if (value < 1) value = 1; else if (value > 10) value = 10;
                if (numTiles != value)
                {
                    numTiles = value;
                    DisposeTiles();
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or sets the selected title. If set to -1, no tile is rendered as selected.
        /// </summary>
        public int SelectedTile
        {
            get { return selectedTile; }
            set
            {
                if (selectedTile != value)
                {
                    selectedTile = value;
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or sets the color for the selected tile.
        /// </summary>
        public Color SelectedColor
        {
            get { return selectedColor; }
            set
            {
                if (selectedColor != value)
                {
                    selectedColor = value;
                    Invalidate();
                }
            }
        }


        /// <summary>
        /// Gets or sets where to place the arrow.
        /// </summary>
        public Placement ArrowPlacement
        {
            get { return arrowPlacement; }
            set
            {
                if (arrowPlacement != value)
                {
                    CheckArrowLocation(value);
                    arrowPlacement = value;
                    Invalidate();
                }
            }
        }


        /// <summary>
        /// Gets or sets the offset for the arrow relative from center.
        /// </summary>
        public int ArrowOffset
        {
            get { return arrowOffset; }
            set
            {
                if (arrowOffset != value)
                {
                    arrowOffset = value;
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or sets the color of the shape.
        /// </summary>
        public override Color Color
        {
            get
            {
                return color;
            }
            set
            {
                if (color != value)
                {
                    color = value;
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets the height or width of the arrow depending on <see cref="P:ArrowLocation"/>.
        /// </summary>
        public int ArrowSize
        {
            get { return arrowShape.Height; }
        }

        #endregion
        #region methods

        protected override Shape CreateClone()
        {
            return new BubbleShape();
        }

        protected override void Copy(Shape shape)
        {
            base.Copy(shape);
            BubbleShape s = shape as BubbleShape;
            s.RenderStyle = RenderStyle;
            s.TileCount = TileCount;
            s.SelectedTile = SelectedTile;
            s.SelectedColor = SelectedColor;
            s.ArrowPlacement = ArrowPlacement;
        }

        private void CreateTiles()
        {
            if (tileShapes == null)
            {
                tileShapes = new BackgroundShape[numTiles];
                for (int i = 0; i < numTiles; i++)
                {
                    Texture texture = GetTileShape(i);
                    tileShapes[i] = new BackgroundShape { Texture = texture, Style = RenderStyle };
                }
            }
        }

        private Texture GetTileShape(int index)
        {
            if (numTiles == 1) return SysImages.Rounded;
            else
            {
                if (index == 0) return SysImages.LeftRounded;
                else return index == numTiles - 1 ? SysImages.RightRounded : SysImages.NotRounded;
            }
        }

        private void DisposeTiles()
        {
            if (tileShapes != null)
            {
                foreach (Shape shape in tileShapes)
                {
                    shape.Dispose();
                }
            }
            tileShapes = null;
        }

        public Rectangle GetTilesRect()
        {
            int x = Location.X;
            int y = Location.Y;
            int top = arrowPlacement == Placement.Top ? arrowShape.Height - arrowOverlapAtTop : 0;
            int w = GetWidth();
            int h = GetHeight();

            return new Rectangle(x, y + top, w, h);
        }

        public Rectangle GetTileRect(int index)
        {
            int x = Location.X;
            int y = Location.Y;
            int top = arrowPlacement == Placement.Top ? arrowShape.Height - arrowOverlapAtTop : 0;
            int w0 = GetWidth();
            int w = w0 / numTiles;
            int h = GetHeight();

            x += index * w;
            if (index == numTiles - 1)
            {
                w = w0 - x;
            }
            else w++;
            return new Rectangle(x, y + top, w, h);
        }

        private int GetWidth()
        {
            switch (arrowPlacement)
            {
                case Placement.Left:
                case Placement.Right:
                    return Width - arrowShape.Width + arrowOverlapAtTop;
                default:
                    return Width;
            }
        }

        private int GetHeight()
        {
            switch (arrowPlacement)
            {
                case Placement.Top:
                case Placement.Bottom:
                    return Height - arrowShape.Height + arrowOverlapAtTop;

                default:
                    return Height;

            }
        }

        public int GetVerticalSpace()
        {
            switch (arrowPlacement)
            {
                case Placement.Hide:
                case Placement.Top:
                case Placement.Bottom:
                    return arrowShape.Height - arrowOverlapAtTop;

                default:
                    return 0;

            }
        }

        protected override void Layout()
        {
            base.Layout();
            CreateTiles();
            Size size = this.Size;
            int h = size.Height - arrowShape.Height;
            int top = arrowPlacement == Placement.Top ? arrowShape.Height - arrowOverlapAtTop : 0;
            int bottom = arrowPlacement == Placement.Top ? 0 : h;

            switch (arrowPlacement)
            {
                case Placement.Top:
                    arrowShape.Transformations = arrowTopTransformation;
                    break;

                case Placement.Hide:
                    break;

                default:
                    arrowShape.Transformations = null;
                    break;
            }

            arrowShape.Color = GetArrowColor(color);
            arrowShape.Visible = arrowPlacement != Placement.Hide;

            int x = Location.X;
            int y = Location.Y;
            arrowShape.Location = new Point(x + (size.Width - arrowShape.Width) / 2 + arrowOffset, y + bottom);

            int w0 = size.Width;
            int w = w0 / numTiles;
            Rectangle bounds = new Rectangle(x, y + top, size.Width, h + arrowOverlapAtTop);

            int index = 0;

            foreach (BackgroundShape shape in tileShapes)
            {
                shape.Color = color;
                shape.Bounds = GetTileRect(index++);
                shape.PreLoad();
            }
            arrowShape.PreLoad();
        }

        private Color GetArrowColor(Color color)
        {
            if (renderStyle == RenderStyle.Edged || renderStyle == RenderStyle.Simple) return color;

            Color[] colors = ColorArrays.GetColors(renderStyle, color);
            switch (arrowPlacement)
            {
                case Placement.Top:
                    return colors[0];
                case Placement.Bottom:
                    return colors[3];
                default:
                    return colors[2];
            }
        }

        //private Color GetArrowColorAtTop(Color color)
        //{
        //    switch (renderStyle)
        //    {
        //        case RenderStyle.Plastic:
        //            return ColorUtil.AlphaBlendColor(color, Color.White, 96);

        //        case RenderStyle.Gradiented:
        //            return ColorUtil.AlphaBlendColor(color, Color.White, 120);

        //        default:
        //            return color;
        //    }
        //}

        //private Color GetArrowColorAtBottom(Color color)
        //{
        //    switch (renderStyle)
        //    {
        //        case RenderStyle.Plastic:
        //            return ColorUtil.AlphaBlendColor(color, Color.Black, 54);

        //        case RenderStyle.Gradiented:
        //            return ColorUtil.AlphaBlendColor(color, Color.Black, 20);

        //        default:
        //            return color;
        //    }
        //}

        /// <summary>
        /// Renders the shape.
        /// The complete shape is rendered two times:
        /// a) with the background color
        /// b) with the selected tile color and settings the scissor box to the range of the selected title.
        /// </summary>
        public override void Render(Renderer renderer)
        {
            RenderShapes(renderer);
            if (selectedTile >= 0)
            {
                RenderShapesSelected(renderer);
            }
        }

        private void RenderShapesSelected(Renderer renderer)
        {
            Rectangle box = renderer.ScissorBox;
            Rectangle box2 = GetTileRect(selectedTile);
            float scaleX = renderer.ScaleX;
            box2.X = (int)(box2.X * scaleX + 0.5f);
            box2.Width = (int)(box2.Width * scaleX + 0.5f);
            box2.X += box.X;
            box2.Y = box.Y;
            //            box2.Offset(box.X, box.Y);
            box2.Height = box.Height;
            renderer.ScissorBox = box2;

            int opacity = (Opacity * renderer.Alpha) >> 8;

            foreach (BackgroundShape shape in tileShapes)
            {
                shape.Color = selectedColor;
                shape.PreLoad();
                renderer.RenderShape(shape, opacity);
                shape.Color = color;
                shape.PreLoad();
            }

            if (arrowShape.Visible)
            {
                arrowShape.Color = GetArrowColor(selectedColor);
                arrowShape.PreLoad();
                renderer.RenderShape(arrowShape, opacity);
                arrowShape.Color = ColorUtil.AlphaBlendColor(color, Color.Black, 54); ;
                arrowShape.PreLoad();
            }

            renderer.ScissorBox = box;
        }

        private void RenderShapes(Renderer renderer)
        {
            int opacity = (this.Opacity * renderer.Alpha) >> 8;
            foreach (BackgroundShape shape in tileShapes) renderer.RenderShape(shape, opacity);
            if (arrowShape.Visible) renderer.RenderShape(arrowShape, opacity);
        }

        private static void CheckArrowLocation(Placement value)
        {
            switch (value)
            {
                case Placement.Left:
                case Placement.Right:
                    throw new NotImplementedException();
            }
        }


        #endregion
    }

}
